using System;
using System.Linq;
using System.Reflection;
using Agatha.Common;
using Agatha.Common.InversionOfControl;
using Agatha.Common.WCF;

namespace Agatha.ServiceLayer
{
	public class ServiceLayerConfiguration
	{
		private readonly Assembly requestHandlersAssembly;
		private readonly Assembly requestsAndResponsesAssembly;
		private readonly IContainer container;

		public Type RequestProcessorImplementation { get; set; }
		public Type AsyncRequestProcessorImplementation { get; set; }
		public IContainer Container { get; private set; }
		public Type ContainerImplementation { get; private set; }
        
        // Made this virtual for tests. Turn into Interface ?
		public virtual Type BusinessExceptionType { get; set; }
        public virtual Type SecurityExceptionType { get; set; }

		public ServiceLayerConfiguration(Assembly requestHandlersAssembly, Assembly requestsAndResponsesAssembly, IContainer container)
		{
			this.requestHandlersAssembly = requestHandlersAssembly;
			this.requestsAndResponsesAssembly = requestsAndResponsesAssembly;
			this.container = container;
			SetDefaultImplementations();
		}

		public ServiceLayerConfiguration(Assembly requestHandlersAssembly, Assembly requestsAndResponsesAssembly, Type containerImplementation)
		{
			this.requestHandlersAssembly = requestHandlersAssembly;
			this.requestsAndResponsesAssembly = requestsAndResponsesAssembly;
			ContainerImplementation = containerImplementation;
			SetDefaultImplementations();
		}

		private void SetDefaultImplementations()
		{
			RequestProcessorImplementation = typeof(RequestProcessor);
			AsyncRequestProcessorImplementation = typeof(AsyncRequestProcessor);
		}

		public void Initialize()
		{
			if (IoC.Container == null)
			{
				IoC.Container = container ?? (IContainer)Activator.CreateInstance(ContainerImplementation);
			}

			IoC.Container.RegisterInstance(this);
			IoC.Container.Register(typeof(IRequestProcessor), RequestProcessorImplementation, Lifestyle.Transient);
			IoC.Container.Register(typeof(IAsyncRequestProcessor), AsyncRequestProcessorImplementation, Lifestyle.Transient);
			RegisterRequestAndResponseTypes();
			RegisterRequestHandlers();
		}

		private void RegisterRequestAndResponseTypes()
		{
			KnownTypeProvider.RegisterDerivedTypesOf<Request>(requestsAndResponsesAssembly);
			KnownTypeProvider.RegisterDerivedTypesOf<Response>(requestsAndResponsesAssembly);
		}

		private void RegisterRequestHandlers()
		{
			var oneWayHandlerType = typeof(OneWayRequestHandler);
			var openOneWayHandlerType = typeof(IOneWayRequestHandler<>);
			var requestResponseHandlerType = typeof(RequestHandler);
			var openRequestReponseHandlerType = typeof(IRequestHandler<>);

			foreach (var type in requestHandlersAssembly.GetTypes())
			{
				if (type.IsAbstract)
					continue;

				if (type.IsSubclassOf(oneWayHandlerType))
				{
					var requestType = GetRequestType(type);

					if (requestType != null)
					{
						var handlerType = openOneWayHandlerType.MakeGenericType(requestType);
						IoC.Container.Register(handlerType, type, Lifestyle.Transient);
					}
					continue;
				}

				if (type.IsSubclassOf(requestResponseHandlerType))
				{
					var requestType = GetRequestType(type);
					if (requestType != null)
					{
						var handlerType = openRequestReponseHandlerType.MakeGenericType(requestType);
						IoC.Container.Register(handlerType, type, Lifestyle.Transient);
					}
				}
			}
		}

		private static Type GetRequestType(Type type)
		{
			if (type.BaseType.IsGenericType)
			{
				// in this case, the Handler inherits from a generic handler interfance (RequestHandler<TRequest, TResponse> or OneWayRequestHandler<TRequest> ) so we need the first generic type
				// argument from the basetype
				return GetFirstGenericTypeArgument(type.BaseType);
			}

			var interfaceType = type.GetInterfaces().FirstOrDefault(i => i.Name.StartsWith("IRequestHandler`") || i.Name.StartsWith("IOneWayRequestHandler`"));

			if (interfaceType == null || interfaceType.GetGenericArguments().Count() == 0)
			{
				return null;
			}

			// in this case, the Handler only inherits from the basic RequestHandler but must implement the 
			// IRequestHandler<TRequest> interface
			return GetFirstGenericTypeArgument(interfaceType);
		}

		private static Type GetFirstGenericTypeArgument(Type type)
		{
			return type.GetGenericArguments()[0];
		}
	}
}